Tests en intégration continue

J.-M. Bruel

Avant-propos

To follow those slides…​

Plan

Pourquoi tester?

Un exemple concret de test obligatoire : Asciidoctor.org

Un exemple concret de documentation obligatoire : Eclipse

Typologie des tests

JUnit etc.

Application concrète pour vos projets

Pourquoi tester ?

 

A majority of the production failures (77%) can be reproduced by a unit test.
— Yuan et al. OSDI 2014
tweet tests
Un tweet récent!

Pour livrer le bon produit

why1

Ce qui marche pour 1 ne marche pas nécessairement pour 100

why2

La loi de Murphy

Tout ce qui est susceptible de mal tourner tournera nécessairement mal.
— Edward A. Murphy Jr.
why3

Différents OS ou différents terminaux

why4

Pour donner le meilleur

why5

Un exemple concret de test obligatoire

Asciidoctor

danAllen
Autour d’une bière avec Dan Allen, à Denver, Colorado #ILoveMyJob

Submitting a Pull Request

  1. Fork the repository.

  2. Run bundle to install development dependencies.

  3. Create a topic branch

  4. Add tests for your unimplemented feature or bug fix. (See [writing-and-executing-tests])

  5. Run bundle exec rake to run the tests. If your tests pass, return to step 4.

  6. Implement your feature or bug fix.

  7. Run bundle exec rake to run the tests. If your tests fail, return to step 6.

  8. Add documentation for your feature or bug fix.

  9. If your changes are not 100% documented, go back to step 8.

  10. Add, commit, and push your changes.

  11. Submit a pull request.

Un exemple concret de documentation obligatoire

Eclipse

gaelBlondelle
Après un footing avec Gaël Blondel, à Saint-Malo #ILoveMyJob

 

[…​] an Eclipse project is providing extensible frameworks and applications accessible via documented APIs.
— Eclipse Development Process

Typologie des tests

 

Vérification

Validation

Le produit est-il bon ?

Le produit est-il le bon ?

Are you building it right?

Are you building the right thing?

Réalisée par le développeur

Réalisée par le testeur

En premier

Après la vérification

JUnit etc.

Quoi tester ?

Les exceptions

@Test (expected = Exception.class)

Le temps d’exécution

@Test(timeout=100)

Uniquement certains environnement

System.getProperty("os.name").contains("Linux")); Attention cette instruction n’est pas une annotation.

S’exécute avant les autres tests (e.g., accès à une base)

@BeforeClass public static void method()

Assertions

fail([message])

On force le test à échouer

assertTrue([message,] condition)

La condition est vraie

assertFalse([message,] condition)

La condition est fausse

assertEquals([message,] attendu, actuel)

Les deux valeurs sont égales

assertNull([message,] object)

Objet nul

assertSame([message,] expected, actual)

Objets identiques (même réf.)

Stratégie de tests

Considérons une fonction int add(int,int); d’une classe myClass.

Cas nominal

Définir le comportement normal de la fonction (sortie normale pour des paramètres corrects).

//for normal addition
@Test
public void testAdd1Plus1() {
  int x  = 1 ; int y = 1;
  assertEquals(2, myClass.add(x,y));
}

Cas particuliers

Ajouter des tests pour les cas particuliers :

  • aucune exception non capturée en cas d'overflow

  • les paramètres null sont gérés, e.g., :

Cas particuliers

//if you are using 0 as default for null, make sure your class works in that case.
@Test
public void testAdd1Plus1() {
  int y = 1;
  assertEquals(0, myClass.add(null,y));
}

Cas particuliers

  • ça marche avec les paramètres négatifs, etc.

L’ordre des tests

Surtout aucun!!

JUnit assumes that all test methods can be executed in an arbitrary order. Well-written test code should not assume any order, i.e., tests should not depend on other tests.
— JUnit manual

Sous Eclipse

Pensez à utiliser le plug-in infinitest.

Et pour les interfaces graphiques?

Exemple de la librairie Robot :

Robot bot = new Robot();
bot.mouseMove(10,10);
bot.mousePress(InputEvent.BUTTON1_MASK);
//add time between press and release or the input event system may
//not think it is a click
try{Thread.sleep(250);}catch(InterruptedException e){}
bot.mouseRelease(InputEvent.BUTTON1_MASK);

 

Couverture des tests

Il existe des outils pour aller plus loin :

coverage

Application concrète pour vos projets

De To Be Done à On going

tuleap1

tuleap2

Mettre à jour le kanban
tuleap3
Confirmation par email

Créer une branche spécifique (si nouvelle feature)

bruel (master) $ git checkout -b US-15378
Switched to a new branch 'US-15378'
bruel (US-15378) $

Ecrire un test qui échoue

Etape 0 : Bien comprendre ce qu’on doit faire

Objectif de la tâche : créer une classe Pile.

Opérations
CréerPile :  -> Pile
estVide   :  Pile ->  Booléen
Empiler   :  Pile * Elément -> Pile
Dépiler   :  Pile -> Pile
Sommet     :  Pile ->  Elément

 

Préconditions
Sommet(p) valide Si et Seulement Si estVide(p) == FAUX
Dépiler(p) valide Si et Seulement Si estVide(p) == FAUX

 

Axiomes
(1) estVide(CréerPile())
(2) estVide(Empiler(p,e)) == FAUX
(3) estVide(Dépiler(Empiler(p,e))) Si et Seulement Si estVide(p)
(4) Sommet(Empiler(p,e)) == e
(5) !estVide(p) => Sommet(Dépiler(Empiler(p,e))) == Sommet(p)

Etape 1 : Ecrire un test simple

import junit.textui.TestRunner;
import junit.framework.TestSuite;
import junit.framework.TestCase;

public class PileTest extends TestCase {

        public void test_type_new_Pile() throws Exception {
                Pile pile = new Pile() ;

                assertEquals("new Pile() retourne une Pile", "Pile", pile.getClass().getName());
        }
}

Oups…​

pile2
Oups, JUnit n’est pas dans le path…​

 

pile3
Création rapide de la classe `Pile`

 

pile1
Run as JUnit Tests

Etape 1' : améliorer avec un main

Pour ceux qui veulent vraiment un main :

public class PileTest extends TestCase {
        static int totalAssertions = 0;
        static int bilanAssertions = 0;

        public void test_type_new_Pile() throws Exception {
                Pile pile = new Pile() ;

                totalAssertions++ ;
                assertEquals("new Pile() retourne une Pile", "Pile", pile.getClass().getName());
                bilanAssertions++ ;
        }

        public static void main(String[] args) {
                junit.textui.TestRunner.run(new TestSuite(PileTest.class));
                if (bilanAssertions == totalAssertions) { System.out.print("Bravo !"); }
                System.out.println(" "+bilanAssertions+"/"+totalAssertions+" assertions vérifiées");
        } // fin main

} // fin PileTest

 

En exécutant le test comme un programme Java on obtient :

...
Time: 0,005

OK (3 tests)

Bravo ! 3/3 assertions vérifiées

 

Si vous utilisez d’autres environnement qu’Eclipse, come SciTE, pour compiler le programme de Test n’oubliez pas de placer les fichiers SciTE.properties et junit.jar dans le répertoire de vos sources (avant d’ouvrir SciTE) ou bien exécutez ceci :

javac -cp .;junit.jar PileTest.java

Etape 2 : écrire un test qui passe

public void test_type_empiler() throws Exception {
  Pile pile = new Pile() ;

  assertEquals("empiler(pile,'XXX') retourne une Pile", "Pile", pile.empiler("XXX").getClass().getName());
}

Erreur de syntaxe

pile4
Erreur de syntaxe

Ajout de la méthode

public class Pile {

	public Object empiler(String string) {
		// TODO Auto-generated method stub
		return this;
	}
}

 

La méthode générée par défaut retourne null ce qui provoque une NullPointerException. Nous avons modifié la méthode en conséquence.

 

pile5
Ajout simple de la méthode manquante

Passage du test

pile6
Passage du test

Etape 2 : écrire un test qui échoue

public void test_axiome1() {
  Pile pile = new Pile() ;

  assertTrue("Une nouvelle pile est vide", pile.estVide(pile));
}

Le test échoue (comme souhaité)

public boolean estVide(Pile pile) {
  // TODO Auto-generated method stub
  return false;
}

Le test échoue (comme souhaité)

pile7
Passage du test

Ignorer un test

Junit utilise l’annotation @Test devant les tests, comme dans l’exemple ci-dessous. Il suffit de l’enlever pour ne pas exécuter ce test :

@Test
public void constructeur_correct() {
  ...
}

Etape 3 : On fait passer le test

public boolean estVide(Pile pile) {
  // Smartly modified by JMB to pass the test!
  return true;
}

Passage du test

pile8
Passage du test

 

Bien sûr le code n’est pas correcte pour l’instant (on s’en rendra compte dès les tests suivants)! Une meilleure solution pourrait être :

public class Pile {
	int count;
  ...
	public boolean estVide(Pile pile) {
		return (count == 0);
	}
}

Essai de merge pour voir si tout le reste marche encore

bruel (US-15378) $ git commit -am "Adding push feature. Tests OK"
[US-15378 78f3242] Adding push feature. Tests OK
 1 file changed, 2 insertions(+), 3 deletions(-)
bruel (US-15378) $ git checkout devs
Switched to branch 'devs'
bruel (devs) $ git merge US-15378

Commit & Push dans devs

bruel (devs) $ git commit -am "..."
...
bruel (devs) $ git push origin devs
...
bruel (devs) $ git branch -D US-15378
Deleted branch US-15378 (was f392a73).

De On going à Review

review
Penser à mettre à jour le tableau de bord

Exemples de perles

Les références absolues!

Position yourself in the project repository (C:\Users\Etudiant\Desktop\Nouveau dossier\MPA2020-Gxxx)
— Anonymous

Les instructions qui ne marchent pas…​

Pour lancer l’application, lancer ant Window
— Extrait de la Doc. Utilisateur
$ ant Window
BUILD FAILED
Target "Window" does not exist in the project ...

Le build.xml qui manque…​

Pour lancer l’application, lancer ant
— Extrait de la Doc. Utilisateur
$ ant
Buildfile: build.xml does not exist!
why3

The End    (for now)